/*$Id: ArtifactHolder.java 2 2010-10-16 20:33:39Z vip.delete $*/

/**
 * Copyright vip_delete
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.googlecode.eclipseplugin;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.factory.ArtifactFactory;
import org.apache.maven.artifact.manager.WagonManager;
import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.resolver.ArtifactResolutionResult;
import org.apache.maven.artifact.resolver.ArtifactResolver;
import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
import org.apache.maven.artifact.resolver.filter.ExcludesArtifactFilter;
import org.apache.maven.artifact.versioning.VersionRange;
import org.apache.maven.model.Dependency;
import org.apache.maven.model.Exclusion;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.project.MavenProject;
import org.apache.maven.wagon.ResourceDoesNotExistException;
import org.apache.maven.wagon.TransferFailedException;

import java.io.File;
import java.util.*;

/**
 * Hold map with all projects and its dependencies
 *
 * @author vip_delete
 * @since 25.07.2010
 */
class ArtifactHolder
{
    /**
     * Compare artifacts for lists
     */
    private static final Comparator<Artifact> ARTIFACT_ID_COMPARATOR = new Comparator<Artifact>()
    {
        @Override
        public int compare(Artifact o1, Artifact o2)
        {
            return o1.getId().compareTo(o2.getId());
        }
    };

    /**
     * Artifacts for all modules
     */
    private List<Artifact> _commonDependencies;

    /**
     * All dependencies of all modules except commonDependencies
     */
    private List<Artifact> _allDependencies;

    /**
     * Maven Project -> Dependency Artifacts except commonDependencies
     */
    private Map<MavenProject, List<Artifact>> _dependencyMap;

    /**
     * Artifacts for all modules
     */
    private Set<Artifact> _reactorArtifacts;

    /**
     * vcs directory mapping
     */
    private Map<String, String> _directory2vcs;

    // local variables for internal use only

    private Log _log;                            // maven logger
    private WagonManager _wagonManager;          // maven downloader
    private ArtifactFactory _artifactFactory;    // maven artifact factory
    private ArtifactRepository _localRepository; // maven local repository
    private List<MavenProject> _reactorProjects; // our reactor projects
    private Set<String> _checkedDestinations;    // file paths already checked
    private Map<MavenProject, Set<Artifact>> _project2transitiveRemoteArtifacts;  // project -> all non reactor artifact dependencies
    private Map<MavenProject, Set<Artifact>> _project2transitiveReactorArtifacts; // project -> all reactor artifact dependencies

    public ArtifactHolder(Log log, WagonManager wagonManager, List<MavenProject> reactorProjects, ArtifactFactory artifactFactory, ArtifactResolver artifactResolver, ArtifactRepository localRepository, ArtifactMetadataSource artifactMetadataSource, boolean downloadSources, boolean downloadJavadocs) throws Exception
    {
        _log = log;
        _wagonManager = wagonManager;
        _reactorProjects = reactorProjects;
        _artifactFactory = artifactFactory;
        _localRepository = localRepository;
        _checkedDestinations = new HashSet<String>();

        // Save reactorArtifacts

        _reactorArtifacts = new TreeSet<Artifact>(ARTIFACT_ID_COMPARATOR);
        for (MavenProject project : reactorProjects)
            _reactorArtifacts.add(project.getArtifact());
        _reactorArtifacts = Collections.unmodifiableSet(_reactorArtifacts);

        // Resolve dependencies

        resolveDependencies();

        Map<MavenProject, Set<Artifact>> dependencyMap = new Hashtable<MavenProject, Set<Artifact>>();
        Set<Artifact> commonDependencies = new LinkedHashSet<Artifact>();
        Set<Artifact> allDependencies = new LinkedHashSet<Artifact>();

        boolean notPassedYet = true;

        for (MavenProject project : reactorProjects)
        {
            if ("pom".equals(project.getPackaging())) continue;

            Set<Artifact> transitiveRemoteArtifacts = _project2transitiveRemoteArtifacts.get(project);

            Set<Artifact> resultArtifacts = new HashSet<Artifact>(_project2transitiveReactorArtifacts.get(project));

            if (!transitiveRemoteArtifacts.isEmpty())
            {
                ArtifactResolutionResult resolutionResult = artifactResolver.resolveTransitively(
                        transitiveRemoteArtifacts,
                        project.getArtifact(),
                        project.getManagedVersionMap(),
                        localRepository,
                        project.getRemoteArtifactRepositories(),
                        artifactMetadataSource
                );

                Set<Artifact> artifacts = resolutionResult.getArtifacts();

                transitiveRemoteArtifacts.addAll(artifacts);

                resultArtifacts.addAll(artifacts);
            }

            allDependencies.addAll(resultArtifacts);

            if (notPassedYet)
            {
                commonDependencies.addAll(resultArtifacts);
                notPassedYet = false;
            }
            else
            {
                for (Iterator<Artifact> iter = commonDependencies.iterator(); iter.hasNext();)
                    if (!resultArtifacts.contains(iter.next()))
                        iter.remove();
            }

            dependencyMap.put(project, resultArtifacts);
        }

        // Save commonDependencies

        _commonDependencies = new ArrayList<Artifact>(commonDependencies);
        Collections.sort(_commonDependencies, ARTIFACT_ID_COMPARATOR);
        _commonDependencies = Collections.unmodifiableList(_commonDependencies);

        // Save allDependencies

        allDependencies.removeAll(commonDependencies);
        _allDependencies = new ArrayList<Artifact>(allDependencies);
        Collections.sort(_allDependencies, ARTIFACT_ID_COMPARATOR);
        _allDependencies = Collections.unmodifiableList(_allDependencies);

        // Save dependencyMap

        _dependencyMap = new Hashtable<MavenProject, List<Artifact>>();
        for (Map.Entry<MavenProject, Set<Artifact>> entry : dependencyMap.entrySet())
        {
            MavenProject mavenProject = entry.getKey();
            List<Artifact> artifacts = new ArrayList<Artifact>(entry.getValue());
            artifacts.removeAll(commonDependencies);
            Collections.sort(artifacts, ARTIFACT_ID_COMPARATOR);
            _dependencyMap.put(mavenProject, Collections.unmodifiableList(artifacts));
        }

        // Save vcs roots

        List<String> directories = new ArrayList<String>();
        for (MavenProject p : reactorProjects)
        {
            String path = p.getFile().getParent() + File.separator;
            if (new File(path, ".svn").exists())
            {
                int i = 0;
                while (i < directories.size() && !path.startsWith(directories.get(i))) i++;
                if (i == directories.size())
                {
                    for (Iterator<String> iter = directories.iterator(); iter.hasNext();)
                        if (iter.next().startsWith(path))
                            iter.remove();
                    directories.add(path);
                }
            }
        }
        _directory2vcs = new TreeMap<String, String>();
        for (String directory : directories)
            _directory2vcs.put(directory, "svn");
        _directory2vcs = Collections.unmodifiableMap(_directory2vcs);

        // Download sources and javadocs

        if (downloadSources) resolveClassifiedDependencies("sources");
        if (downloadJavadocs) resolveClassifiedDependencies("javadoc");

        // Free memory

        _log = null;
        _wagonManager = null;
        _reactorProjects = null;
        _artifactFactory = null;
        _checkedDestinations = null;
        _project2transitiveRemoteArtifacts = null;
        _project2transitiveReactorArtifacts = null;
    }

    private void resolveDependencies() throws Exception
    {
        Map<Artifact, Set<Artifact>> project2remoteArtifacts = new Hashtable<Artifact, Set<Artifact>>();
        Map<Artifact, Set<Artifact>> project2reactorArtifacts = new Hashtable<Artifact, Set<Artifact>>();

        for (MavenProject project : _reactorProjects)
        {
            Set<Artifact> remoteDependencies = new HashSet<Artifact>();
            Set<Artifact> reactorDependencies = new HashSet<Artifact>();

            for (Dependency dependency : (List<Dependency>) project.getDependencies())
            {
                Artifact artifact = _artifactFactory.createDependencyArtifact(
                        dependency.getGroupId(),
                        dependency.getArtifactId(),
                        VersionRange.createFromVersionSpec(dependency.getVersion()),
                        dependency.getType(),
                        dependency.getClassifier(),
                        dependency.getScope() == null ? Artifact.SCOPE_COMPILE : dependency.getScope(),
                        dependency.isOptional()
                );

                if (_reactorArtifacts.contains(artifact))
                    reactorDependencies.add(artifact);
                else if (remoteDependencies.add(artifact))
                {
                    if (Artifact.SCOPE_SYSTEM.equalsIgnoreCase(dependency.getScope()))
                        artifact.setFile(new File(dependency.getSystemPath()));

                    List<String> exclusions = new ArrayList<String>();
                    for (Exclusion exclusion : dependency.getExclusions())
                        exclusions.add(exclusion.getGroupId() + ":" + exclusion.getArtifactId());

                    ArtifactFilter artifactFilter = new ExcludesArtifactFilter(exclusions);

                    artifact.setDependencyFilter(artifactFilter);
                }
            }

            project2remoteArtifacts.put(project.getArtifact(), remoteDependencies);
            project2reactorArtifacts.put(project.getArtifact(), reactorDependencies);
        }

        _project2transitiveRemoteArtifacts = new Hashtable<MavenProject, Set<Artifact>>();
        _project2transitiveReactorArtifacts = new Hashtable<MavenProject, Set<Artifact>>();
        for (MavenProject project : _reactorProjects)
        {
            Set<Artifact> remoteResult = new HashSet<Artifact>();
            Set<Artifact> reactorResult = new HashSet<Artifact>();
            resolveRecurseArtifacts(project.getArtifact(), remoteResult, reactorResult, project2remoteArtifacts, project2reactorArtifacts);
            _project2transitiveRemoteArtifacts.put(project, remoteResult);
            _project2transitiveReactorArtifacts.put(project, reactorResult);
        }
    }

    private void resolveRecurseArtifacts(Artifact artifact,
                                         Set<Artifact> remoteResult, Set<Artifact> reactorResult,
                                         Map<Artifact, Set<Artifact>> remoteDependencies,
                                         Map<Artifact, Set<Artifact>> reactorDependecies)
    {
        remoteResult.addAll(remoteDependencies.get(artifact));
        reactorResult.addAll(reactorDependecies.get(artifact));
        for (Artifact a : reactorDependecies.get(artifact))
            resolveRecurseArtifacts(a, remoteResult, reactorResult, remoteDependencies, reactorDependecies);
    }

    private void resolveClassifiedDependencies(String classifier)
    {
        for (MavenProject project : _reactorProjects)
            for (Artifact artifact : _project2transitiveRemoteArtifacts.get(project))
                resolveClassifiedArtifact(project.getRemoteArtifactRepositories(), artifact, classifier);
    }

    private void resolveClassifiedArtifact(List remoteRepositories, Artifact artifact, String classifier)
    {
        String basePath = _localRepository.pathOf(artifact);

        if (!basePath.endsWith(".jar")) return;

        File destination = new File(_localRepository.getBasedir(), basePath.substring(0, basePath.length() - 4) + "-" + classifier + ".jar");

        if (!_checkedDestinations.add(destination.getAbsolutePath())) return;

        if (!artifact.isSnapshot() && destination.exists()) return;

        Artifact classifiedArtifact = _artifactFactory.createArtifactWithClassifier(artifact.getGroupId(), artifact.getArtifactId(), artifact.getVersion(), artifact.getType(), classifier);

        classifiedArtifact.setFile(destination);

        try
        {
            _wagonManager.getArtifact(classifiedArtifact, remoteRepositories);
        }
        catch (TransferFailedException e)
        {
            _log.debug(e);
        }
        catch (ResourceDoesNotExistException e)
        {
            _log.debug(e);
        }
    }

    public List<Artifact> getDependencies(MavenProject project)
    {
        List<Artifact> artifacts = _dependencyMap.get(project);

        return artifacts == null ? Collections.<Artifact>emptyList() : artifacts;
    }

    public List<Artifact> getCommonDependencies()
    {
        return _commonDependencies;
    }

    public List<Artifact> getAllDependencies()
    {
        return _allDependencies;
    }

    public boolean isReactorArtifact(Artifact artifact)
    {
        return _reactorArtifacts.contains(artifact);
    }

    public Map<String, String> getVcsMapping()
    {
        return _directory2vcs;
    }

    public List<MavenProject> getProjectsWithPackaging(String packaging)
    {
        List<MavenProject> projects = new ArrayList<MavenProject>();
        for (MavenProject project : _dependencyMap.keySet())
            if (project.getPackaging().equals(packaging))
                projects.add(project);
        return projects;
    }
}
